﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;

namespace FasterWPF
{
    public static class AnimationExt
    {
        public static void RunInUiThread(this Dispatcher thisDispatcher, Action action)
        {
            thisDispatcher.Invoke(action);
        }

        public static IDisposable WaitFor(this Dispatcher thisDispatcher, TimeSpan duration, Action action)
        {
            IDisposable dispTimer = Observable.Timer(duration).Subscribe(t =>
            {
                 thisDispatcher.RunInUiThread(() =>
                 {
                     action();
                 });
             });
            return dispTimer;
        }

        //GroupAnimations?

        public static void FadeOutFadeIn(this Border elementOut, Border elementIn, double seconds, Action actionAtTheEnd)
        {
            elementOut.FadeOut(seconds);
          
            elementIn.FadeIn(seconds);

            elementIn.Dispatcher.WaitFor(TimeSpan.FromSeconds(seconds), () =>
            {
                actionAtTheEnd();
            });
        }

        public static DoubleAnimation FadeOut(this Border theElement, double seconds)
        {
            return theElement.DoubleAnimate(Border.OpacityProperty, 1.00D, 0.00D, TimeSpan.FromSeconds(seconds), false, false);
        }

        public static DoubleAnimation FadeIn(this Border theElement, double seconds)
        {
            return theElement.DoubleAnimate(Border.OpacityProperty, 0.00D, 1.00D, TimeSpan.FromSeconds(seconds), false, false);
        }

        public enum ScaleType
        {
            BottomToTop=0,
            RightToLeft=1
        }

        public static void ScaleOutScaleIn(this Border elementOut, Border elementIn, ScaleType scaleType, double seconds, Action actionAtTheEnd, Panel p = null, Color? panelColor = null, double colorSeconds = .3)
        {
            elementOut.ScaleOut(scaleType, seconds);
            elementOut.FadeOut(seconds);
            if (p != null && panelColor!=null)
            {
                p.SolidColorInPanelBackground((Color)panelColor, colorSeconds);
            }
            elementIn.ScaleIn(scaleType, seconds);
            elementIn.TranslateIn(scaleType, elementOut.ActualWidth, seconds);
            elementIn.Dispatcher.WaitFor(TimeSpan.FromSeconds(seconds), () =>
            {
                actionAtTheEnd();
            });
        }

        public static DoubleAnimation ScaleOut(this Border theElement, ScaleType scaleType, double seconds)
        {
            return theElement.ScaleAnimate(scaleType == ScaleType.RightToLeft ? ScaleTransform.ScaleXProperty : ScaleTransform.ScaleYProperty, 1.00D, 0.00D, TimeSpan.FromSeconds(seconds), false, false);
        }

        public static DoubleAnimation ScaleIn(this Border theElement, ScaleType scaleType, double seconds)
        {
            return theElement.ScaleAnimate(scaleType == ScaleType.RightToLeft ? ScaleTransform.ScaleXProperty : ScaleTransform.ScaleYProperty, 0.00D, 1.00D, TimeSpan.FromSeconds(seconds), false, false);
        }

        public static ColorAnimation SolidColorInPanelBackground<P>(this P theElement, Color colorTo, double seconds)
            where P: Panel
        {
            return theElement.ColorAnimatePanelBackground(SolidColorBrush.ColorProperty, colorTo, TimeSpan.FromSeconds(seconds), false, false);
        }

        public static DoubleAnimation TranslateIn(this Border theElement, ScaleType scaleType, double startingXPosition, double seconds)
        {
            return theElement.TranslateAnimate(scaleType == ScaleType.RightToLeft ? TranslateTransform.XProperty : TranslateTransform.YProperty, startingXPosition, 0.00D, TimeSpan.FromSeconds(seconds), false, false);
        }

        public static DoubleAnimation DoubleAnimate(this FrameworkElement theElement, DependencyProperty dp, double from, double to, System.TimeSpan duration, bool autoreverse, bool forever )
        {
            DoubleAnimation myDoubleAnimation = new DoubleAnimation();
            myDoubleAnimation.From = from;
            myDoubleAnimation.To = to;
            myDoubleAnimation.Duration = new Duration(duration);
            myDoubleAnimation.AutoReverse = autoreverse;
            if (forever)
            {
                myDoubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
            }

            theElement.BeginAnimation(dp, myDoubleAnimation);

            return myDoubleAnimation;
        }
        
        public static DoubleAnimation ScaleAnimate(this FrameworkElement theElement, DependencyProperty dp, double from, double to, System.TimeSpan duration, bool autoreverse, bool forever)
        {
            DoubleAnimation myDoubleAnimation = new DoubleAnimation();
            myDoubleAnimation.From = from;
            myDoubleAnimation.To = to;
            myDoubleAnimation.Duration = new Duration(duration);
            myDoubleAnimation.AutoReverse = autoreverse;
            myDoubleAnimation.EasingFunction = new CircleEase { EasingMode = EasingMode.EaseOut };
            if (forever)
            {
                myDoubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
            }

            ScaleTransform scT = new ScaleTransform();
            theElement.RenderTransform = scT;
            scT.BeginAnimation(dp, myDoubleAnimation);

            return myDoubleAnimation;
        }

        public static DoubleAnimation TranslateAnimate(this FrameworkElement theElement, DependencyProperty dp, double from, double to, System.TimeSpan duration, bool autoreverse, bool forever)
        {
            DoubleAnimation myDoubleAnimation = new DoubleAnimation();
            myDoubleAnimation.From = from;
            myDoubleAnimation.To = to;
            myDoubleAnimation.Duration = new Duration(duration);
            myDoubleAnimation.AutoReverse = autoreverse;
            myDoubleAnimation.EasingFunction = new CircleEase { EasingMode = EasingMode.EaseOut };
            if (forever)
            {
                myDoubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
            } 
            
            TranslateTransform myTranslate = new TranslateTransform();
            theElement.RenderTransform = myTranslate;
            myTranslate.BeginAnimation(dp, myDoubleAnimation);

            return myDoubleAnimation;
        }

        public static ColorAnimation ColorAnimatePanelBackground<P>(this P thePanel, DependencyProperty dp, Color to, System.TimeSpan duration, bool autoreverse, bool forever)
            where P: Panel
        {
            ColorAnimation myDoubleAnimation = new ColorAnimation();
            myDoubleAnimation.To = to;
            myDoubleAnimation.Duration = new Duration(duration);
            myDoubleAnimation.AutoReverse = autoreverse;
            myDoubleAnimation.EasingFunction = new CircleEase { EasingMode = EasingMode.EaseOut };
            if (forever)
            {
                myDoubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
            }

            Brush b = null;

            if ((thePanel).Background != null)
            {
                b = (SolidColorBrush)(thePanel).Background.Clone();
            }
            else
            {
                b = new SolidColorBrush();
            }
            
            (thePanel).Background = b;
            b.BeginAnimation(dp, myDoubleAnimation);

            return myDoubleAnimation;
        }
        
//RotateTransform myRotateTransform = new RotateTransform(); 
//myRotateTransform.Angle = 0; 
 
//SkewTransform mySkew = new SkewTransform (); 
//mySkew.AngleX=0; 
//mySkew.AngleY=0; 
 
        public static IDisposable WaitForXSeconds(this FrameworkElement theElement, double seconds, Action actionToRun)
        {
            IDisposable dispB = Observable.Timer(TimeSpan.FromSeconds(seconds)).Subscribe(t =>
            {
                theElement.Dispatcher.RunInUiThread(() =>
               {
                   actionToRun();
               });
            });

            return dispB;
        }

        public static IDisposable WaitForXSeconds(this FrameworkElement theElement, double seconds, out IDisposable dispB, Action actionToRun)
        {
            dispB = Observable.Timer(TimeSpan.FromSeconds(seconds)).Subscribe(t =>
            {
                theElement.Dispatcher.RunInUiThread(() =>
                {
                    actionToRun();
                });
            });

            return dispB;
        }
    }
}
